package com.coldworks.base.lib.menudrawer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.coldworks.coldjoke.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;

public abstract class MenuDrawer extends ViewGroup {

	protected final Logger log = LoggerFactory.getLogger(this.getClass());

	/**
	 * Callback interface for changing state of the drawer.
	 */
	public interface OnDrawerStateChangeListener {

		/**
		 * Called when the drawer state changes.
		 * 
		 * @param oldState
		 *            The old drawer state.
		 * @param newState
		 *            The new drawer state.
		 */
		void onDrawerStateChange(int oldState, int newState);
	}

	/**
	 * Tag used when logging.
	 */
	private static final String TAG = "MenuDrawer";

	/**
	 * Indicates whether debug code should be enabled.
	 */
	private static final boolean DEBUG = false;

	/**
	 * Key used when saving menu visibility state.
	 */
	private static final String STATE_MENU_VISIBLE = "net.simonvt.menudrawer.view.menu.menuVisible";

	/**
	 * The time between each frame when animating the drawer.
	 */
	protected static final int ANIMATION_DELAY = 1000 / 60;

	/**
	 * Interpolator used for stretching/retracting the arrow indicator.
	 */
	protected static final Interpolator ARROW_INTERPOLATOR = new AccelerateInterpolator();

	/**
	 * Interpolator used for peeking at the drawer.
	 */
	private static final Interpolator PEEK_INTERPOLATOR = new PeekInterpolator();

	/**
	 * Interpolator used when animating the drawer open/closed.
	 */
	private static final Interpolator SMOOTH_INTERPOLATOR = new SmoothInterpolator();

	/**
	 * Default delay from {@link #peekDrawer()} is called until first animation
	 * is run.
	 */
	private static final long DEFAULT_PEEK_START_DELAY = 5000;

	/**
	 * Default delay between each subsequent animation, after
	 * {@link #peekDrawer()} has been called.
	 */
	private static final long DEFAULT_PEEK_DELAY = 10000;

	/**
	 * The duration of the peek animation.
	 */
	private static final int PEEK_DURATION = 5000;

	/**
	 * The maximum touch area width of the drawer in dp.
	 */
	private static final int MAX_DRAG_BEZEL_DP = 24;

	/**
	 * The maximum animation duration.
	 */
	private static final int DURATION_MAX = 600;

	/**
	 * The maximum alpha of the dark menu overlay used for dimming the menu.
	 */
	protected static final int MAX_MENU_OVERLAY_ALPHA = 185;

	/**
	 * Drag mode for sliding only the content view.
	 */
	public static final int MENU_DRAG_CONTENT = 0;

	/**
	 * Drag mode for sliding the entire window.
	 */
	public static final int MENU_DRAG_WINDOW = 1;

	/**
	 * Position the menu to the left of the content.
	 */
	public static final int MENU_POSITION_LEFT = 0;

	/**
	 * Position the menu to the right of the content.
	 */
	public static final int MENU_POSITION_RIGHT = 1;

	/**
	 * Disallow opening the drawer by dragging the screen.
	 */
	public static final int TOUCH_MODE_NONE = 0;

	/**
	 * Allow opening drawer only by dragging on the edge of the screen.
	 */
	public static final int TOUCH_MODE_BEZEL = 1;

	/**
	 * Allow opening drawer by dragging anywhere on the screen.
	 */
	public static final int TOUCH_MODE_FULLSCREEN = 2;

	/**
	 * Indicates that the drawer is currently closed.
	 */
	public static final int STATE_CLOSED = 0;

	/**
	 * Indicates that the drawer is currently closing.
	 */
	public static final int STATE_CLOSING = 1;

	/**
	 * Indicates that the drawer is currently being dragged by the user.
	 */
	public static final int STATE_DRAGGING = 2;

	/**
	 * Indicates that the drawer is currently opening.
	 */
	public static final int STATE_OPENING = 4;

	/**
	 * Indicates that the drawer is currently open.
	 */
	public static final int STATE_OPEN = 8;

	/**
	 * Distance in dp from closed position from where the drawer is considered
	 * closed with regards to touch events.
	 */
	private static final int CLOSE_ENOUGH = 3;

	/**
	 * Indicates whether to use {@link View#setTranslationX(float)} when
	 * positioning views.
	 */
	static final boolean USE_TRANSLATIONS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;

	/**
	 * Drawable used as menu overlay.
	 */
	protected Drawable mMenuOverlay;

	/**
	 * Defines whether the drop shadow is enabled.
	 */
	private boolean mDropShadowEnabled;

	/**
	 * Drawable used as content drop shadow onto the menu.
	 */
	protected Drawable mDropShadowDrawable;

	/**
	 * The width of the content drop shadow.
	 */
	protected int mDropShadowWidth;

	/**
	 * Arrow bitmap used to indicate the active view.
	 */
	protected Bitmap mArrowBitmap;

	/**
	 * The currently active view.
	 */
	protected View mActiveView;

	/**
	 * Position of the active view. This is compared to
	 * View#getTag(R.id.mdActiveViewPosition) when drawing the arrow.
	 */
	protected int mActivePosition;

	/**
	 * Used when reading the position of the active view.
	 */
	protected final Rect mActiveRect = new Rect();

	/**
	 * The parent of the menu view.
	 */
	protected BuildLayerFrameLayout mMenuContainer;

	/**
	 * The parent of the content view.
	 */
	protected BuildLayerFrameLayout mContentView;

	/**
	 * The width of the menu.
	 */
	protected int mMenuWidth;

	/**
	 * Indicates whether the menu width has been set explicity either via the
	 * theme or by calling {@link #setMenuWidth(int)}.
	 */
	private boolean mMenuWidthSet;

	/**
	 * Current left position of the content.
	 */
	protected int mOffsetPixels;

	/**
	 * Indicates whether the menu is currently visible.
	 */
	protected boolean mMenuVisible;

	/**
	 * The drag mode of the drawer. Can be either {@link #MENU_DRAG_CONTENT} or
	 * {@link #MENU_DRAG_WINDOW}.
	 */
	private int mDragMode;

	/**
	 * The current drawer state.
	 * 
	 * @see #STATE_CLOSED
	 * @see #STATE_CLOSING
	 * @see #STATE_DRAGGING
	 * @see #STATE_OPENING
	 * @see #STATE_OPEN
	 */
	private int mDrawerState = STATE_CLOSED;

	/**
	 * The maximum touch area width of the drawer in px.
	 */
	protected int mMaxTouchBezelWidth;

	/**
	 * The touch area width of the drawer in px.
	 */
	protected int mTouchWidth;

	/**
	 * Indicates whether the drawer is currently being dragged.
	 */
	protected boolean mIsDragging;

	/**
	 * Slop before starting a drag.
	 */
	protected final int mTouchSlop;

	/**
	 * The initial X position of a drag.
	 */
	protected float mInitialMotionX;

	/**
	 * The last X position of a drag.
	 */
	protected float mLastMotionX = -1;

	/**
	 * The last Y position of a drag.
	 */
	protected float mLastMotionY = -1;

	/**
	 * Runnable used when animating the drawer open/closed.
	 */
	private final Runnable mDragRunnable = new Runnable() {
		public void run() {
			postAnimationInvalidate();
		}
	};

	/**
	 * Runnable used when the peek animation is running.
	 */
	protected final Runnable mPeekRunnable = new Runnable() {
		@Override
		public void run() {
			peekDrawerInvalidate();
		}
	};

	/**
	 * Runnable used for first call to {@link #startPeek()} after
	 * {@link #peekDrawer()} has been called.
	 */
	private Runnable mPeekStartRunnable;

	/**
	 * Default delay between each subsequent animation, after
	 * {@link #peekDrawer()} has been called.
	 */
	protected long mPeekDelay;

	/**
	 * Scroller used when animating the drawer open/closed.
	 */
	private Scroller mScroller;

	/**
	 * Scroller used for the peek drawer animation.
	 */
	protected Scroller mPeekScroller;

	/**
	 * Velocity tracker used when animating the drawer open/closed after a drag.
	 */
	protected VelocityTracker mVelocityTracker;

	/**
	 * Maximum velocity allowed when animating the drawer open/closed.
	 */
	protected int mMaxVelocity;

	/**
	 * Listener used to dispatch state change events.
	 */
	private OnDrawerStateChangeListener mOnDrawerStateChangeListener;

	/**
	 * Indicates whether the menu should be offset when dragging the drawer.
	 */
	protected boolean mOffsetMenu = true;

	/**
	 * Touch mode for the Drawer. Possible values are {@link #TOUCH_MODE_NONE},
	 * {@link #TOUCH_MODE_BEZEL} or {@link #TOUCH_MODE_FULLSCREEN} Default:
	 * {@link #TOUCH_MODE_BEZEL}
	 */
	protected int mTouchMode = TOUCH_MODE_BEZEL;

	/**
	 * Distance in px from closed position from where the drawer is considered
	 * closed with regards to touch events.
	 */
	protected int mCloseEnough;

	/**
	 * Indicates whether the current layer type is
	 * {@link View#LAYER_TYPE_HARDWARE}.
	 */
	private boolean mLayerTypeHardware;

	/**
	 * Indicates whether to use {@link View#LAYER_TYPE_HARDWARE} when animating
	 * the drawer.
	 */
	private boolean mHardwareLayersEnabled = true;

	public MenuDrawer(Context context) {
		this(context, null);
	}

	public MenuDrawer(Context context, AttributeSet attrs) {
		this(context, attrs, R.attr.menuDrawerStyle);
	}

	public MenuDrawer(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		setWillNotDraw(false);
		setFocusable(false);

		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.MenuDrawer, defStyle, R.style.Widget_MenuDrawer);

		final Drawable contentBackground = a
				.getDrawable(R.styleable.MenuDrawer_mdContentBackground);
		final Drawable menuBackground = a
				.getDrawable(R.styleable.MenuDrawer_mdMenuBackground);

		mMenuWidth = a.getDimensionPixelSize(
				R.styleable.MenuDrawer_mdMenuWidth, -1);
		mMenuWidthSet = mMenuWidth != -1;

		final int arrowResId = a.getResourceId(
				R.styleable.MenuDrawer_mdArrowDrawable, 0);
		if (arrowResId != 0) {
			mArrowBitmap = BitmapFactory.decodeResource(getResources(),
					arrowResId);
		}

		mDropShadowEnabled = a.getBoolean(
				R.styleable.MenuDrawer_mdDropShadowEnabled, true);

		mDropShadowDrawable = a
				.getDrawable(R.styleable.MenuDrawer_mdDropShadow);

		if (mDropShadowDrawable == null) {
			final int dropShadowColor = a.getColor(
					R.styleable.MenuDrawer_mdDropShadowColor, 0xFF000000);
			setDropShadowColor(dropShadowColor);
		}

		mDropShadowWidth = a.getDimensionPixelSize(
				R.styleable.MenuDrawer_mdDropShadowWidth, dpToPx(6));

		a.recycle();

		mMenuContainer = new BuildLayerFrameLayout(context);
		mMenuContainer.setId(R.id.md__menu);
		mMenuContainer.setBackgroundDrawable(menuBackground);
		addView(mMenuContainer);

		mContentView = new NoClickThroughFrameLayout(context);
		mContentView.setId(R.id.md__content);
		mContentView.setBackgroundDrawable(contentBackground);
		addView(mContentView);

		mMenuOverlay = new ColorDrawable(0xFF000000);

		final ViewConfiguration configuration = ViewConfiguration.get(context);
		mTouchSlop = configuration.getScaledTouchSlop();
		mMaxVelocity = configuration.getScaledMaximumFlingVelocity();

		mScroller = new Scroller(context, SMOOTH_INTERPOLATOR);
		mPeekScroller = new Scroller(context, PEEK_INTERPOLATOR);

		mMaxTouchBezelWidth = dpToPx(MAX_DRAG_BEZEL_DP);
		mCloseEnough = dpToPx(CLOSE_ENOUGH);
	}

	private int dpToPx(int dp) {
		return (int) (getResources().getDisplayMetrics().density * dp + 0.5f);
	}

	/**
	 * Toggles the menu open and close with animation.
	 */
	public void toggleMenu() {
		toggleMenu(true);
	}

	/**
	 * Toggles the menu open and close.
	 * 
	 * @param animate
	 *            Whether open/close should be animated.
	 */
	public void toggleMenu(boolean animate) {
		if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
			closeMenu(animate);
		} else if (mDrawerState == STATE_CLOSED
				|| mDrawerState == STATE_CLOSING) {
			openMenu(animate);
		}
	}

	/**
	 * Animates the menu open.
	 */
	public void openMenu() {
		openMenu(true);
	}

	/**
	 * Opens the menu.
	 * 
	 * @param animate
	 *            Whether open/close should be animated.
	 */
	public void openMenu(boolean animate) {
		animateOffsetTo(mMenuWidth, 0, animate);
	}

	/**
	 * Animates the menu closed.
	 */
	public void closeMenu() {
		closeMenu(true);
	}

	/**
	 * Closes the menu.
	 * 
	 * @param animate
	 *            Whether open/close should be animated.
	 */
	public void closeMenu(boolean animate) {
		animateOffsetTo(0, 0, animate);
	}

	/**
	 * Indicates whether the menu is currently visible.
	 * 
	 * @return True if the menu is open, false otherwise.
	 */
	public boolean isMenuVisible() {
		return mMenuVisible;
	}

	/**
	 * Set the width of the menu drawer when open.
	 * 
	 * @param width
	 */
	public void setMenuWidth(final int width) {
		mMenuWidth = width;
		mMenuWidthSet = true;
		if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
			setOffsetPixels(mMenuWidth);
		}
		requestLayout();
		invalidate();
	}

	/**
	 * Set the active view. If the mdArrowDrawable attribute is set, this View
	 * will have an arrow drawn next to it.
	 * 
	 * @param v
	 *            The active view.
	 * @param position
	 *            Optional position, usually used with ListView.
	 *            v.setTag(R.id.mdActiveViewPosition, position) must be called
	 *            first.
	 */
	public void setActiveView(View v, int position) {
		mActiveView = v;
		mActivePosition = position;
		invalidate();
	}

	/**
	 * Enables or disables offsetting the menu when dragging the drawer.
	 * 
	 * @param offsetMenu
	 *            True to offset the menu, false otherwise.
	 */
	public void setOffsetMenuEnabled(boolean offsetMenu) {
		if (offsetMenu != mOffsetMenu) {
			mOffsetMenu = offsetMenu;
			requestLayout();
			invalidate();
		}
	}

	/**
	 * Indicates whether the menu is being offset when dragging the drawer.
	 * 
	 * @return True if the menu is being offset, false otherwise.
	 */
	public boolean getOffsetMenuEnabled() {
		return mOffsetMenu;
	}

	/**
	 * Returns the state of the drawer. Can be one of {@link #STATE_CLOSED},
	 * {@link #STATE_CLOSING}, {@link #STATE_DRAGGING}, {@link #STATE_OPENING}
	 * or {@link #STATE_OPEN}.
	 * 
	 * @return The drawers state.
	 */
	public int getDrawerState() {
		return mDrawerState;
	}

	/**
	 * Register a callback to be invoked when the drawer state changes.
	 * 
	 * @param listener
	 *            The callback that will run.
	 */
	public void setOnDrawerStateChangeListener(
			OnDrawerStateChangeListener listener) {
		mOnDrawerStateChangeListener = listener;
	}

	/**
	 * Defines whether the drop shadow is enabled.
	 * 
	 * @param enabled
	 *            Whether the drop shadow is enabled.
	 */
	public void setDropShadowEnabled(boolean enabled) {
		mDropShadowEnabled = enabled;
		invalidate();
	}

	/**
	 * Sets the color of the drop shadow.
	 * 
	 * @param color
	 *            The color of the drop shadow.
	 */
	public abstract void setDropShadowColor(int color);

	/**
	 * Sets the drawable of the drop shadow.
	 * 
	 * @param drawable
	 *            The drawable of the drop shadow.
	 */
	public void setDropShadow(Drawable drawable) {
		mDropShadowDrawable = drawable;
		invalidate();
	}

	/**
	 * Sets the drawable of the drop shadow.
	 * 
	 * @param resId
	 *            The resource identifier of the the drawable.
	 */
	public void setDropShadow(int resId) {
		setDropShadow(getResources().getDrawable(resId));
	}

	/**
	 * Returns the drawable of the drop shadow.
	 */
	public Drawable getDropShadow() {
		return mDropShadowDrawable;
	}

	/**
	 * Sets the width of the drop shadow.
	 * 
	 * @param width
	 *            The width of the drop shadow in px.
	 */
	public void setDropShadowWidth(int width) {
		mDropShadowWidth = width;
		invalidate();
	}

	/**
	 * Animates the drawer slightly open until the user opens the drawer.
	 */
	public void peekDrawer() {
		peekDrawer(DEFAULT_PEEK_START_DELAY, DEFAULT_PEEK_DELAY);
	}

	/**
	 * Animates the drawer slightly open. If delay is larger than 0, this
	 * happens until the user opens the drawer.
	 * 
	 * @param delay
	 *            The delay (in milliseconds) between each run of the animation.
	 *            If 0, this animation is only run once.
	 */
	public void peekDrawer(long delay) {
		peekDrawer(DEFAULT_PEEK_START_DELAY, delay);
	}

	/**
	 * Animates the drawer slightly open. If delay is larger than 0, this
	 * happens until the user opens the drawer.
	 * 
	 * @param startDelay
	 *            The delay (in milliseconds) until the animation is first run.
	 * @param delay
	 *            The delay (in milliseconds) between each run of the animation.
	 *            If 0, this animation is only run once.
	 */
	public void peekDrawer(final long startDelay, final long delay) {
		if (startDelay < 0) {
			throw new IllegalArgumentException(
					"startDelay must be zero or larger.");
		}
		if (delay < 0) {
			throw new IllegalArgumentException("delay must be zero or larger");
		}

		removeCallbacks(mPeekRunnable);
		removeCallbacks(mPeekStartRunnable);

		mPeekDelay = delay;
		mPeekStartRunnable = new Runnable() {
			@Override
			public void run() {
				startPeek();
			}
		};
		postDelayed(mPeekStartRunnable, startDelay);
	}

	/**
	 * Enables or disables the user of {@link View#LAYER_TYPE_HARDWARE} when
	 * animations views.
	 * 
	 * @param enabled
	 *            Whether hardware layers are enabled.
	 */
	public void setHardwareLayerEnabled(boolean enabled) {
		if (enabled != mHardwareLayersEnabled) {
			mHardwareLayersEnabled = enabled;
			mMenuContainer.setHardwareLayersEnabled(enabled);
			mContentView.setHardwareLayersEnabled(enabled);
			stopLayerTranslation();
		}
	}

	/**
	 * Sets the drawer state.
	 * 
	 * @param state
	 *            The drawer state. Must be one of {@link #STATE_CLOSED},
	 *            {@link #STATE_CLOSING}, {@link #STATE_DRAGGING},
	 *            {@link #STATE_OPENING} or {@link #STATE_OPEN}.
	 */
	protected void setDrawerState(int state) {
		if (state != mDrawerState) {
			final int oldState = mDrawerState;
			mDrawerState = state;
			if (mOnDrawerStateChangeListener != null)
				mOnDrawerStateChangeListener.onDrawerStateChange(oldState,
						state);
			if (DEBUG)
				logDrawerState(state);
		}
	}

	private void logDrawerState(int state) {
		switch (state) {
		case STATE_CLOSED:
			Log.d(TAG, "[DrawerState] STATE_CLOSED");
			break;

		case STATE_CLOSING:
			Log.d(TAG, "[DrawerState] STATE_CLOSING");
			break;

		case STATE_DRAGGING:
			Log.d(TAG, "[DrawerState] STATE_DRAGGING");
			break;

		case STATE_OPENING:
			Log.d(TAG, "[DrawerState] STATE_OPENING");
			break;

		case STATE_OPEN:
			Log.d(TAG, "[DrawerState] STATE_OPEN");
			break;

		default:
			Log.d(TAG, "[DrawerState] Unknown: " + state);
		}
	}

	/**
	 * Sets the drawer drag mode. Can be either {@link #MENU_DRAG_CONTENT} or
	 * {@link #MENU_DRAG_WINDOW}.
	 * 
	 * @param dragMode
	 *            The drag mode.
	 */
	public void setDragMode(int dragMode) {
		mDragMode = dragMode;
	}

	/**
	 * Returns the touch mode.
	 */
	public int getTouchMode() {
		return mTouchMode;
	}

	/**
	 * Sets the drawer touch mode. Possible values are {@link #TOUCH_MODE_NONE},
	 * {@link #TOUCH_MODE_BEZEL} or {@link #TOUCH_MODE_FULLSCREEN}.
	 * 
	 * @param dragMode
	 *            The drag mode.
	 */
	public void setTouchMode(int mode) {
		if (mTouchMode != mode) {
			mTouchMode = mode;
			updateTouchAreaWidth();
		}
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		final int offsetPixels = mOffsetPixels;

		drawMenuOverlay(canvas, offsetPixels);
		if (mDropShadowEnabled)
			drawDropShadow(canvas, offsetPixels);
		if (mArrowBitmap != null)
			drawArrow(canvas, offsetPixels);
	}

	/**
	 * Called when the content drop shadow should be drawn.
	 * 
	 * @param canvas
	 *            The canvas on which to draw.
	 * @param offsetPixels
	 *            Value in pixels indicating the offset.
	 */
	protected abstract void drawDropShadow(Canvas canvas, int offsetPixels);

	/**
	 * Called when the menu overlay should be drawn.
	 * 
	 * @param canvas
	 *            The canvas on which to draw.
	 * @param offsetPixels
	 *            Value in pixels indicating the offset.
	 */
	protected abstract void drawMenuOverlay(Canvas canvas, int offsetPixels);

	/**
	 * Called when the arrow indicator should be drawn.
	 * 
	 * @param canvas
	 *            The canvas on which to draw.
	 * @param offsetPixels
	 *            Value in pixels indicating the offset.
	 */
	protected abstract void drawArrow(Canvas canvas, int offsetPixels);

	/**
	 * Sets the number of pixels the content should be offset.
	 * 
	 * @param offsetPixels
	 *            The number of pixels to offset the content by.
	 */
	protected void setOffsetPixels(int offsetPixels) {
		if (offsetPixels != mOffsetPixels) {
			onOffsetPixelsChanged(offsetPixels);

			mOffsetPixels = offsetPixels;
			mMenuVisible = offsetPixels != 0;
		}
	}

	/**
	 * Called when the number of pixels the content should be offset by has
	 * changed.
	 * 
	 * @param offsetPixels
	 *            The number of pixels to offset the content by.
	 */
	protected abstract void onOffsetPixelsChanged(int offsetPixels);

	/**
	 * If possible, set the layer type to {@link View#LAYER_TYPE_HARDWARE}.
	 */
	protected void startLayerTranslation() {
		if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
			mLayerTypeHardware = true;
			mContentView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
			mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
		}
	}

	/**
	 * If the current layer type is {@link View#LAYER_TYPE_HARDWARE}, this will
	 * set it to @link View#LAYER_TYPE_NONE}.
	 */
	private void stopLayerTranslation() {
		if (mLayerTypeHardware) {
			mLayerTypeHardware = false;
			mContentView.setLayerType(View.LAYER_TYPE_NONE, null);
			mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

		if (widthMode != MeasureSpec.EXACTLY
				|| heightMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException("Must measure with an exact size");
		}

		final int width = MeasureSpec.getSize(widthMeasureSpec);
		final int height = MeasureSpec.getSize(heightMeasureSpec);

		if (!mMenuWidthSet)
			mMenuWidth = (int) (width * 0.8f);
		if (mOffsetPixels == -1)
			setOffsetPixels(mMenuWidth);

		final int menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
				0, mMenuWidth);
		final int menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
				0, height);
		mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);

		final int contentWidthMeasureSpec = getChildMeasureSpec(
				widthMeasureSpec, 0, width);
		final int contentHeightMeasureSpec = getChildMeasureSpec(
				widthMeasureSpec, 0, height);
		mContentView.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);

		setMeasuredDimension(width, height);

		updateTouchAreaWidth();
	}

	@Override
	protected boolean fitSystemWindows(Rect insets) {
		if (mDragMode == MENU_DRAG_WINDOW) {
			mMenuContainer.setPadding(0, insets.top, 0, 0);
		}
		return super.fitSystemWindows(insets);
	}

	/**
	 * Compute the touch area based on the touched mode.
	 * 
	 * @param dragMode
	 *            The drag mode.
	 */
	private void updateTouchAreaWidth() {
		if (mTouchMode == TOUCH_MODE_BEZEL) {
			mTouchWidth = Math
					.min(getMeasuredWidth() / 10, mMaxTouchBezelWidth);
		} else if (mTouchMode == TOUCH_MODE_FULLSCREEN) {
			mTouchWidth = getMeasuredWidth();
		} else {
			mTouchWidth = 0;
		}
	}

	/**
	 * Called when a drag has been ended.
	 */
	private void endDrag() {
		mIsDragging = false;

		if (mVelocityTracker != null) {
			mVelocityTracker.recycle();
			mVelocityTracker = null;
		}
	}

	/**
	 * Stops ongoing animation of the drawer.
	 */
	protected void stopAnimation() {
		removeCallbacks(mDragRunnable);
		mScroller.abortAnimation();
		stopLayerTranslation();
	}

	/**
	 * Called when a drawer animation has successfully completed.
	 */
	private void completeAnimation() {
		mScroller.abortAnimation();
		final int finalX = mScroller.getFinalX();
		setOffsetPixels(finalX);
		setDrawerState(finalX == 0 ? STATE_CLOSED : STATE_OPEN);
		stopLayerTranslation();
	}

	/**
	 * Moves the drawer to the position passed.
	 * 
	 * @param position
	 *            The position the content is moved to.
	 * @param velocity
	 *            Optional velocity if called by releasing a drag event.
	 * @param animate
	 *            Whether the move is animated.
	 */
	protected void animateOffsetTo(int position, int velocity, boolean animate) {
		endDrag();
		endPeek();

		final int startX = mOffsetPixels;
		final int dx = position - startX;
		if (dx == 0 || !animate) {
			setOffsetPixels(position);
			setDrawerState(position == 0 ? STATE_CLOSED : STATE_OPEN);
			stopLayerTranslation();
			return;
		}

		int duration;

		velocity = Math.abs(velocity);
		if (velocity > 0) {
			duration = 4 * Math.round(1000.f * Math.abs((float) dx / velocity));
		} else {
			duration = (int) (600.f * Math.abs((float) dx / mMenuWidth));
		}

		duration = Math.min(duration, DURATION_MAX);

		if (dx > 0) {
			setDrawerState(STATE_OPENING);
			mScroller.startScroll(startX, 0, dx, 0, duration);
		} else {
			setDrawerState(STATE_CLOSING);
			mScroller.startScroll(startX, 0, dx, 0, duration);
		}

		startLayerTranslation();

		postAnimationInvalidate();
	}

	/**
	 * Callback when each frame in the drawer animation should be drawn.
	 */
	private void postAnimationInvalidate() {
		if (mScroller.computeScrollOffset()) {
			final int oldX = mOffsetPixels;
			final int x = mScroller.getCurrX();

			if (x != oldX)
				setOffsetPixels(x);
			if (x != mScroller.getFinalX()) {
				postOnAnimation(mDragRunnable);
				return;
			}
		}

		completeAnimation();
	}

	/**
	 * Starts peek drawer animation.
	 */
	protected void startPeek() {

		peekListener.onPeekStart();
		final int menuWidth = mMenuWidth;
		final int dx = menuWidth / 3;
		mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);

		startLayerTranslation();
		peekDrawerInvalidate();
	}

	/**
	 * Callback when each frame in the peek drawer animation should be drawn.
	 */
	private void peekDrawerInvalidate() {
		if (mPeekScroller.computeScrollOffset()) {
			final int oldX = mOffsetPixels;
			final int x = mPeekScroller.getCurrX();
			if (x != oldX)
				setOffsetPixels(x);

			if (!mPeekScroller.isFinished()) {
				postOnAnimation(mPeekRunnable);
				return;

			} else if (mPeekDelay > 0) {
				mPeekStartRunnable = new Runnable() {
					@Override
					public void run() {
						startPeek();
					}
				};
				postDelayed(mPeekStartRunnable, mPeekDelay);
			}
		}

		completePeek();
	}

	/**
	 * 监听滑动Menu拖动事件
	 */
	private DragMenuListener dragMenuListener;

	public interface DragMenuListener {
		void onDragMenu(boolean isDragging);
	}

	public void setOnDragMenuListener(DragMenuListener listener) {
		this.dragMenuListener = listener;
	}

	/**
	 * 监听Peek事件完成后的Listener
	 */
	private PeekListener peekListener;

	public void setOnPeekListener(PeekListener listener) {
		this.peekListener = listener;
	}

	public interface PeekListener {
		void onPeekStart();

		void onPeekEnd();
	}

	/**
	 * Called when the peek drawer animation has successfully completed.
	 */
	private void completePeek() {
		mPeekScroller.abortAnimation();
		setOffsetPixels(0);
		setDrawerState(STATE_CLOSED);
		stopLayerTranslation();
	}

	/**
	 * Stops ongoing peek drawer animation.
	 */
	protected void endPeek() {
		removeCallbacks(mPeekStartRunnable);
		removeCallbacks(mPeekRunnable);
		stopLayerTranslation();
		peekListener.onPeekEnd();
	}

	@Override
	public void postOnAnimation(Runnable action) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
			super.postOnAnimation(action);
		} else {
			postDelayed(action, ANIMATION_DELAY);
		}
	}

	protected boolean isCloseEnough() {
		return mOffsetPixels <= mCloseEnough;
	}

	/**
	 * Returns true if the touch event occurs over the content.
	 * 
	 * @param ev
	 *            The motion event.
	 * @return True if the touch event occured over the content, false
	 *         otherwise.
	 */
	protected abstract boolean isContentTouch(MotionEvent ev);

	/**
	 * Returns true if dragging the content should be allowed.
	 * 
	 * @param ev
	 *            The motion event.
	 * @return True if dragging the content should be allowed, false otherwise.
	 */
	protected abstract boolean onDownAllowDrag(MotionEvent ev);

	/**
	 * Returns true if dragging the content should be allowed.
	 * 
	 * @param ev
	 *            The motion event.
	 * @return True if dragging the content should be allowed, false otherwise.
	 */
	protected abstract boolean onMoveAllowDrag(MotionEvent ev, float dx);

	/**
	 * Called when a move event has happened while dragging the content is in
	 * progress.
	 * 
	 * @param dx
	 *            The X difference between the last motion event and the current
	 *            motion event.
	 */
	protected abstract void onMoveEvent(float dx);

	public void setMenuDragEnableD(boolean isSlide) {
		this.isDrag = isSlide;
	}

	public boolean isMenuDragEnabled() {
		return isDrag;
	}

	protected boolean isDrag = true;

	/**
	 * Called when {@link MotionEvent#ACTION_UP} of
	 * {@link MotionEvent#ACTION_CANCEL} is delivered to
	 * {@link MenuDrawer#onTouchEvent(android.view.MotionEvent)}.
	 * 
	 * @param ev
	 *            The motion event.
	 */
	protected abstract void onUpEvent(MotionEvent ev);

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		if (!isDrag) {
			return false;
		}

		final int action = ev.getAction() & MotionEvent.ACTION_MASK;

		if (action == MotionEvent.ACTION_DOWN && mMenuVisible
				&& isCloseEnough()) {
			setOffsetPixels(0);
			stopAnimation();
			endPeek();
			setDrawerState(STATE_CLOSED);
		}

		// Always intercept events over the content while menu is visible.
		if (mMenuVisible && isContentTouch(ev))
			return true;

		if (mTouchMode == TOUCH_MODE_NONE) {
			return false;
		}

		if (action != MotionEvent.ACTION_DOWN) {
			if (mIsDragging)
				return true;
		}

		switch (action) {
		case MotionEvent.ACTION_DOWN: {
			mLastMotionX = mInitialMotionX = ev.getX();
			mLastMotionY = ev.getY();
			final boolean allowDrag = onDownAllowDrag(ev);

			if (allowDrag) {
				setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
				stopAnimation();
				endPeek();
				mIsDragging = false;
			}
			break;
		}

		case MotionEvent.ACTION_MOVE: {
			final float x = ev.getX();
			final float dx = x - mLastMotionX;
			final float xDiff = Math.abs(dx);
			final float y = ev.getY();
			final float yDiff = Math.abs(y - mLastMotionY);

			if (xDiff > mTouchSlop && xDiff > yDiff) {
				final boolean allowDrag = onMoveAllowDrag(ev, dx);

				if (allowDrag) {
					setDrawerState(STATE_DRAGGING);
					mIsDragging = true;
					mLastMotionX = x;
					mLastMotionY = y;
				}
			}
			break;
		}

		/**
		 * If you click really fast, an up or cancel event is delivered here.
		 * Just snap content to whatever is closest.
		 * */
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP: {
			final int offsetPixels = mOffsetPixels;
			animateOffsetTo(offsetPixels > mMenuWidth / 2 ? mMenuWidth : 0, 0,
					true);
			break;
		}
		}

		if (mVelocityTracker == null)
			mVelocityTracker = VelocityTracker.obtain();
		mVelocityTracker.addMovement(ev);

		return mIsDragging;
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (!mMenuVisible && (mTouchMode == TOUCH_MODE_NONE)) {
			return false;
		}
		final int action = ev.getAction() & MotionEvent.ACTION_MASK;

		if (mVelocityTracker == null)
			mVelocityTracker = VelocityTracker.obtain();
		mVelocityTracker.addMovement(ev);

		switch (action) {
		case MotionEvent.ACTION_DOWN: {
			mLastMotionX = mInitialMotionX = ev.getX();
			mLastMotionY = ev.getY();
			final boolean allowDrag = onDownAllowDrag(ev);

			if (allowDrag) {
				stopAnimation();
				endPeek();
				startLayerTranslation();
			}
			break;
		}

		case MotionEvent.ACTION_MOVE: {
			if (!mIsDragging) {
				final float x = ev.getX();
				final float dx = x - mLastMotionX;
				final float xDiff = Math.abs(dx);
				final float y = ev.getY();
				final float yDiff = Math.abs(y - mLastMotionY);

				if (xDiff > mTouchSlop && xDiff > yDiff) {
					final boolean allowDrag = onMoveAllowDrag(ev, dx);

					if (allowDrag) {
						setDrawerState(STATE_DRAGGING);
						mIsDragging = true;
						mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX
								+ mTouchSlop
								: mInitialMotionX - mTouchSlop;
					}
				}
			}

			if (this.dragMenuListener != null) {
				this.dragMenuListener.onDragMenu(mIsDragging);
			}

			if (mIsDragging) {
				startLayerTranslation();

				final float x = ev.getX();
				final float dx = x - mLastMotionX;
				mLastMotionX = x;
				onMoveEvent(dx);
			}
			break;
		}

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP: {
			onUpEvent(ev);
			break;
		}
		}

		return true;
	}

	/**
	 * Saves the state of the drawer.
	 * 
	 * @return Returns a Parcelable containing the drawer state.
	 */
	public Parcelable saveState() {
		Bundle state = new Bundle();
		final boolean menuVisible = mDrawerState == STATE_OPEN
				|| mDrawerState == STATE_OPENING;
		state.putBoolean(STATE_MENU_VISIBLE, menuVisible);
		return state;
	}

	/**
	 * Restores the state of the drawer.
	 * 
	 * @param in
	 *            A parcelable containing the drawer state.
	 */
	public void restoreState(Parcelable in) {
		Bundle state = (Bundle) in;
		final boolean menuOpen = state.getBoolean(STATE_MENU_VISIBLE);
		setOffsetPixels(menuOpen ? mMenuWidth : 0);
		mDrawerState = menuOpen ? STATE_OPEN : STATE_CLOSED;
	}
}
